// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <cmath>
#include <cstring>

#include "parquet/geospatial/util_internal.h"
#include "parquet/test_util.h"

namespace parquet::geospatial {

TEST(TestGeometryUtil, TestBoundingBox) {
  BoundingBox box;
  EXPECT_EQ(box, BoundingBox({kInf, kInf, kInf, kInf}, {-kInf, -kInf, -kInf, -kInf}));
  EXPECT_EQ(box.ToString(),
            "BoundingBox\n  x: [inf, -inf]\n  y: [inf, -inf]\n  z: [inf, -inf]\n  m: "
            "[inf, -inf]\n");

  BoundingBox box_xyzm({-1, -2, -3, -4}, {1, 2, 3, 4});
  BoundingBox box_xy({-10, -20, kInf, kInf}, {10, 20, -kInf, -kInf});
  BoundingBox box_xyz({kInf, kInf, -30, kInf}, {-kInf, -kInf, 30, -kInf});
  BoundingBox box_xym({kInf, kInf, kInf, -40}, {-kInf, -kInf, -kInf, 40});

  box_xyzm.Merge(box_xy);
  EXPECT_EQ(box_xyzm, BoundingBox({-10, -20, -3, -4}, {10, 20, 3, 4}));
  EXPECT_EQ(box_xyzm.ToString(),
            "BoundingBox\n  x: [-10, 10]\n  y: [-20, 20]\n  z: [-3, 3]\n  m: "
            "[-4, 4]\n");

  box_xyzm.Merge(box_xyz);
  EXPECT_EQ(box_xyzm, BoundingBox({-10, -20, -30, -4}, {10, 20, 30, 4}));

  box_xyzm.Merge(box_xym);
  EXPECT_EQ(box_xyzm, BoundingBox({-10, -20, -30, -40}, {10, 20, 30, 40}));

  double nan_dbl = std::numeric_limits<double>::quiet_NaN();
  BoundingBox box_nan({nan_dbl, nan_dbl, nan_dbl, nan_dbl},
                      {nan_dbl, nan_dbl, nan_dbl, nan_dbl});
  box_xyzm.Merge(box_nan);
  for (int i = 0; i < 4; i++) {
    EXPECT_TRUE(std::isnan(box_xyzm.min[i]));
    EXPECT_TRUE(std::isnan(box_xyzm.max[i]));
  }

  box_xyzm.Reset();
  EXPECT_EQ(box_xyzm, BoundingBox());
}

TEST(TestGeometryUtil, TestBoundingBoxEquals) {
  BoundingBox box_a({-1, -2, -3, -4}, {1, 2, 3, 4});
  BoundingBox box_b({-1, -2, -3, -4}, {1, 2, 3, 4});

  for (int i = 0; i < 4; i++) {
    // Set one min component to another value and ensure inequality
    box_b.min[i] = -1000;
    EXPECT_NE(box_a, box_b);

    // Reset the min component and ensure equality
    box_b.min = box_a.min;
    EXPECT_EQ(box_a, box_b);

    // Set one max component to another value and ensure inequality
    box_b.max[i] = -1000;
    EXPECT_NE(box_a, box_b);

    // Reset the max component and ensure equality
    box_b.max = box_a.max;
    EXPECT_EQ(box_a, box_b);
  }
}

struct WKBTestCase {
  WKBTestCase() = default;
  WKBTestCase(GeometryType geometry_type, Dimensions dimensions,
              const std::vector<uint8_t>& wkb, const std::vector<double>& box_values = {})
      : geometry_type(geometry_type), dimensions(dimensions), wkb(wkb) {
    std::array<double, 4> mins = {kInf, kInf, kInf, kInf};
    std::array<double, 4> maxes{-kInf, -kInf, -kInf, -kInf};

    if (dimensions == Dimensions::kXYM) {
      mins = {box_values[0], box_values[1], kInf, box_values[2]};
      maxes = {box_values[3], box_values[4], -kInf, box_values[5]};
    } else {
      size_t coord_size = box_values.size() / 2;
      for (uint32_t i = 0; i < coord_size; i++) {
        mins[i] = box_values[i];
        maxes[i] = box_values[coord_size + i];
      }
    }

    box = BoundingBox(mins, maxes);
  }
  WKBTestCase(const WKBTestCase& other) = default;

  GeometryType geometry_type;
  Dimensions dimensions;
  std::vector<uint8_t> wkb;
  BoundingBox box;
};

std::ostream& operator<<(std::ostream& os, const WKBTestCase& obj) {
  uint32_t iso_wkb_geometry_type =
      static_cast<int>(obj.dimensions) * 1000 + static_cast<int>(obj.geometry_type);
  os << "WKBTestCase<" << iso_wkb_geometry_type << ">";
  return os;
}

class WKBTestFixture : public ::testing::TestWithParam<WKBTestCase> {
 protected:
  WKBTestCase test_case;
};

TEST_P(WKBTestFixture, TestWKBBounderBounds) {
  auto item = GetParam();

  WKBGeometryBounder bounder;
  EXPECT_EQ(bounder.Bounds(), BoundingBox());

  ASSERT_NO_THROW(bounder.MergeGeometry(item.wkb));

  EXPECT_EQ(bounder.Bounds(), item.box);
  uint32_t wkb_type =
      static_cast<int>(item.dimensions) * 1000 + static_cast<int>(item.geometry_type);
  EXPECT_THAT(bounder.GeometryTypes(), ::testing::ElementsAre(::testing::Eq(wkb_type)));

  bounder.Reset();
  EXPECT_EQ(bounder.Bounds(), BoundingBox());
  EXPECT_TRUE(bounder.GeometryTypes().empty());
}

TEST_P(WKBTestFixture, TestWKBBounderErrorForTruncatedInput) {
  auto item = GetParam();
  WKBGeometryBounder bounder;

  // Make sure an error occurs for any version of truncated input to the bounder
  for (size_t i = 0; i < item.wkb.size(); i++) {
    SCOPED_TRACE(i);
    ASSERT_THROW(bounder.MergeGeometry({item.wkb.data(), i}), ParquetException);
  }
}

TEST_P(WKBTestFixture, TestWKBBounderErrorForInputWithTooManyBytes) {
  auto item = GetParam();
  WKBGeometryBounder bounder;

  std::vector<uint8_t> wkb_with_extra_byte(item.wkb.size() + 1);
  std::memcpy(wkb_with_extra_byte.data(), item.wkb.data(), item.wkb.size());
  wkb_with_extra_byte[item.wkb.size()] = 0x00;

  ASSERT_THROW(bounder.MergeGeometry(wkb_with_extra_byte), ParquetException);
}

INSTANTIATE_TEST_SUITE_P(
    TestGeometryUtil, WKBTestFixture,
    ::testing::Values(
        // POINT EMPTY
        WKBTestCase(GeometryType::kPoint, Dimensions::kXY,
                    {0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0xf8, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x7f},
                    {kInf, kInf, -kInf, -kInf}),
        // POINT (30 10)
        WKBTestCase(GeometryType::kPoint, Dimensions::kXY,
                    {0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40},
                    {30, 10, 30, 10}),
        // POINT Z (30 10 40)
        WKBTestCase(GeometryType::kPoint, Dimensions::kXYZ,
                    {0x01, 0xe9, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24,
                     0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40},
                    {30, 10, 40, 30, 10, 40}),
        // POINT M (30 10 300)
        WKBTestCase(GeometryType::kPoint, Dimensions::kXYM,
                    {0x01, 0xd1, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24,
                     0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x72, 0x40},
                    {30, 10, 300, 30, 10, 300}),
        // POINT ZM (30 10 40 300)
        WKBTestCase(GeometryType::kPoint, Dimensions::kXYZM,
                    {0x01, 0xb9, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24,
                     0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0xc0, 0x72, 0x40},
                    {30, 10, 40, 300, 30, 10, 40, 300}),
        // POINT (30 10) (big endian)
        WKBTestCase(GeometryType::kPoint, Dimensions::kXY,
                    {0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x3e, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x40, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
                    {30, 10, 30, 10}),
        // LINESTRING EMPTY
        WKBTestCase(GeometryType::kLinestring, Dimensions::kXY,
                    {0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
                    {kInf, kInf, -kInf, -kInf}),
        // LINESTRING (30 10, 10 30, 40 40)
        WKBTestCase(GeometryType::kLinestring, Dimensions::kXY,
                    {0x01, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e,
                     0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40},
                    {10, 10, 40, 40}),
        // LINESTRING Z (30 10 40, 10 30 40, 40 40 80)
        WKBTestCase(GeometryType::kLinestring, Dimensions::kXYZ,
                    {0x01, 0xea, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x54, 0x40},
                    {10, 10, 40, 40, 40, 80}),
        // LINESTRING M (30 10 300, 10 30 300, 40 40 1600)
        WKBTestCase(GeometryType::kLinestring, Dimensions::kXYM,
                    {0x01, 0xd2, 0x07, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x72, 0x40,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0,
                     0x72, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x99, 0x40},
                    {10, 10, 300, 40, 40, 1600}),
        // LINESTRING ZM (30 10 40 300, 10 30 40 300, 40 40 80 1600)
        WKBTestCase(GeometryType::kLinestring, Dimensions::kXYZM,
                    {0x01, 0xba, 0x0b, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x72, 0x40, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0xc0, 0x72, 0x40, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44,
                     0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x40, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x99, 0x40},
                    {10, 10, 40, 300, 40, 40, 80, 1600}),
        // LINESTRING (30 10, 10 30, 40 40) (big endian)
        WKBTestCase(GeometryType::kLinestring, Dimensions::kXY,
                    {0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x40,
                     0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x24, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x24, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x40, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x40, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
                     0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
                    {10, 10, 40, 40}),
        // POLYGON EMPTY
        WKBTestCase(GeometryType::kPolygon, Dimensions::kXY,
                    {0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
                    {kInf, kInf, -kInf, -kInf}),
        // POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))
        WKBTestCase(GeometryType::kPolygon, Dimensions::kXY,
                    {0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44,
                     0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x40, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x40,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x24, 0x40},
                    {10, 10, 40, 40}),
        // POLYGON Z ((30 10 40, 40 40 80, 20 40 60, 10 20 30, 30 10 40))
        WKBTestCase(
            GeometryType::kPolygon, Dimensions::kXYZ,
            {0x01, 0xeb, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44,
             0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x40, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x34, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x40, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x44, 0x40},
            {10, 10, 30, 40, 40, 80}),
        // POLYGON M ((30 10 300, 40 40 1600, 20 40 800, 10 20 200, 30 10 300))
        WKBTestCase(
            GeometryType::kPolygon, Dimensions::kXYM,
            {0x01, 0xd3, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x72, 0x40, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44,
             0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x40, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x34, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x40, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x69, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
             0xc0, 0x72, 0x40},
            {10, 10, 200, 40, 40, 1600}),
        // POLYGON ZM ((30 10 40 300, 40 40 80 1600, 20 40 60 800, 10 20 30 200, 30 10 40
        // 300))
        WKBTestCase(
            GeometryType::kPolygon, Dimensions::kXYZM,
            {0x01, 0xbb, 0x0b, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00,
             0x00, 0x00, 0x00, 0xc0, 0x72, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44,
             0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x54, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x40, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x40, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x89, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x40, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24,
             0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00,
             0x00, 0xc0, 0x72, 0x40},
            {10, 10, 30, 200, 40, 40, 80, 1600}),
        // MULTIPOINT EMPTY
        WKBTestCase(GeometryType::kMultiPoint, Dimensions::kXY,
                    {0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
                    {kInf, kInf, -kInf, -kInf}),
        // MULTIPOINT ((30 10))
        WKBTestCase(GeometryType::kMultiPoint, Dimensions::kXY,
                    {0x01, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
                     0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40},
                    {30, 10, 30, 10}),
        // MULTIPOINT Z ((30 10 40))
        WKBTestCase(GeometryType::kMultiPoint, Dimensions::kXYZ,
                    {0x01, 0xec, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
                     0xe9, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40},
                    {30, 10, 40, 30, 10, 40}),
        // MULTIPOINT M ((30 10 300))
        WKBTestCase(GeometryType::kMultiPoint, Dimensions::kXYM,
                    {0x01, 0xd4, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
                     0xd1, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x72, 0x40},
                    {30, 10, 300, 30, 10, 300}),
        // MULTIPOINT ZM ((30 10 40 300))
        WKBTestCase(GeometryType::kMultiPoint, Dimensions::kXYZM,
                    {0x01, 0xbc, 0x0b, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
                     0xb9, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0xc0, 0x72, 0x40},
                    {30, 10, 40, 300, 30, 10, 40, 300}),
        // MULTILINESTRING EMPTY
        WKBTestCase(GeometryType::kMultiLinestring, Dimensions::kXY,
                    {0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
                    {kInf, kInf, -kInf, -kInf}),
        // MULTILINESTRING ((30 10, 10 30, 40 40))
        WKBTestCase(GeometryType::kMultiLinestring, Dimensions::kXY,
                    {0x01, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02,
                     0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24,
                     0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40},
                    {10, 10, 40, 40}),
        // MULTILINESTRING Z ((30 10 40, 10 30 40, 40 40 80))
        WKBTestCase(
            GeometryType::kMultiLinestring, Dimensions::kXYZ,
            {0x01, 0xed, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xea, 0x03, 0x00,
             0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44,
             0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x40},
            {10, 10, 40, 40, 40, 80}),
        // MULTILINESTRING M ((30 10 300, 10 30 300, 40 40 1600))
        WKBTestCase(
            GeometryType::kMultiLinestring, Dimensions::kXYM,
            {0x01, 0xd5, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xd2, 0x07, 0x00,
             0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
             0xc0, 0x72, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x72,
             0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x40},
            {10, 10, 300, 40, 40, 1600}),
        // MULTILINESTRING ZM ((30 10 40 300, 10 30 40 300, 40 40 80 1600))
        WKBTestCase(
            GeometryType::kMultiLinestring, Dimensions::kXYZM,
            {0x01, 0xbd, 0x0b, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xba, 0x0b, 0x00,
             0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x72, 0x40, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e,
             0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00,
             0x00, 0xc0, 0x72, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x54, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x40},
            {10, 10, 40, 300, 40, 40, 80, 1600}),
        // MULTIPOLYGON EMPTY
        WKBTestCase(GeometryType::kMultiPolygon, Dimensions::kXY,
                    {0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
                    {kInf, kInf, -kInf, -kInf}),
        // MULTIPOLYGON (((30 10, 40 40, 20 40, 10 20, 30 10)))
        WKBTestCase(
            GeometryType::kMultiPolygon, Dimensions::kXY,
            {0x01, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00,
             0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x40, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40},
            {10, 10, 40, 40}),
        // MULTIPOLYGON Z (((30 10 40, 40 40 80, 20 40 60, 10 20 30, 30 10 40)))
        WKBTestCase(
            GeometryType::kMultiPolygon, Dimensions::kXYZ,
            {0x01, 0xee, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xeb, 0x03, 0x00,
             0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x54, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x40,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x4e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x34, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e,
             0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40},
            {10, 10, 30, 40, 40, 80}),
        // MULTIPOLYGON M (((30 10 300, 40 40 1600, 20 40 800, 10 20 200, 30 10 300)))
        WKBTestCase(
            GeometryType::kMultiPolygon, Dimensions::kXYM,
            {0x01, 0xd6, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xd3, 0x07, 0x00,
             0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00,
             0x00, 0x00, 0x00, 0x00, 0xc0, 0x72, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x99, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x40,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x89, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x34, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69,
             0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x72, 0x40},
            {10, 10, 200, 40, 40, 1600}),
        // MULTIPOLYGON ZM (((30 10 40 300, 40 40 80 1600, 20 40 60 800, 10 20 30 200, 30
        // 10 40 300)))
        WKBTestCase(GeometryType::kMultiPolygon, Dimensions::kXYZM,
                    {0x01, 0xbe, 0x0b, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xbb,
                     0x0b, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x72, 0x40, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54,
                     0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x40, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x34, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x40,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x40, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x34, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x40, 0x00, 0x00, 0x00, 0x00,
                     0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24,
                     0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0xc0, 0x72, 0x40},
                    {10, 10, 30, 200, 40, 40, 80, 1600}),
        // GEOMETRYCOLLECTION EMPTY
        WKBTestCase(GeometryType::kGeometryCollection, Dimensions::kXY,
                    {0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
                    {kInf, kInf, -kInf, -kInf}),
        // GEOMETRYCOLLECTION (POINT (30 10))
        WKBTestCase(GeometryType::kGeometryCollection, Dimensions::kXY,
                    {0x01, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
                     0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40},
                    {30, 10, 30, 10}),
        // GEOMETRYCOLLECTION Z (POINT Z (30 10 40))
        WKBTestCase(GeometryType::kGeometryCollection, Dimensions::kXYZ,
                    {0x01, 0xef, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
                     0xe9, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40},
                    {30, 10, 40, 30, 10, 40}),
        // GEOMETRYCOLLECTION M (POINT M (30 10 300))
        WKBTestCase(GeometryType::kGeometryCollection, Dimensions::kXYM,
                    {0x01, 0xd7, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
                     0xd1, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x72, 0x40},
                    {30, 10, 300, 30, 10, 300}),
        // GEOMETRYCOLLECTION ZM (POINT ZM (30 10 40 300))
        WKBTestCase(GeometryType::kGeometryCollection, Dimensions::kXYZM,
                    {0x01, 0xbf, 0x0b, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
                     0xb9, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                     0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40,
                     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x40, 0x00, 0x00,
                     0x00, 0x00, 0x00, 0xc0, 0x72, 0x40},
                    {30, 10, 40, 300, 30, 10, 40, 300})));

struct MakeWKBPointTestCase {
  std::vector<double> xyzm;
  bool has_z{};
  bool has_m{};
};

std::ostream& operator<<(std::ostream& os, const MakeWKBPointTestCase& obj) {
  os << "MakeWKBPointTestCase<has_z=" << obj.has_z << ", has_m=" << obj.has_m << ">";
  return os;
}

class MakeWKBPointTestFixture : public testing::TestWithParam<MakeWKBPointTestCase> {};

TEST_P(MakeWKBPointTestFixture, MakeWKBPoint) {
  const auto& param = GetParam();
  std::string wkb = test::MakeWKBPoint(param.xyzm, param.has_z, param.has_m);
  WKBGeometryBounder bounder;
  ASSERT_NO_THROW(bounder.MergeGeometry(wkb));
  const BoundingBox::XYZM& mins = bounder.Bounds().min;
  EXPECT_DOUBLE_EQ(param.xyzm[0], mins[0]);
  EXPECT_DOUBLE_EQ(param.xyzm[1], mins[1]);
  if (param.has_z) {
    EXPECT_DOUBLE_EQ(param.xyzm[2], mins[2]);
  } else {
    EXPECT_TRUE(std::isinf(mins[2]));
  }
  if (param.has_m) {
    EXPECT_DOUBLE_EQ(param.xyzm[3], mins[3]);
  } else {
    EXPECT_TRUE(std::isinf(mins[3]));
  }
}

INSTANTIATE_TEST_SUITE_P(
    TestGeometryUtil, MakeWKBPointTestFixture,
    ::testing::Values(MakeWKBPointTestCase{{30, 10, 40, 300}, false, false},
                      MakeWKBPointTestCase{{30, 10, 40, 300}, true, false},
                      MakeWKBPointTestCase{{30, 10, 40, 300}, false, true},
                      MakeWKBPointTestCase{{30, 10, 40, 300}, true, true}));

}  // namespace parquet::geospatial
